---
title: 指针和引用
---

在 C++ 中，指针和引用是允许直接内存操作和高效数据传递的基本概念。它们对于理解 C++ 如何管理内存以及与硬件交互至关重要，并且它们与 JavaScript 的内存模型有显著差异。

## 指针概念和语法

**指针**是一个存储另一个变量内存地址的变量。它“指向”内存中存储值的位置。指针功能强大，但需要仔细处理。

### 声明和初始化指针

*   `int* ptr;` // 声明一个指向整数的指针。
*   `int* ptr = &variable;` // 使用 `variable` 的地址初始化 `ptr`。

### 解引用指针

*   `*ptr` // 访问存储在 `ptr` 中内存地址的值。

<UniversalEditor title="指针基础比较" compare={true}>
```javascript !! js
// JavaScript: 没有直接的指针，变量持有值或对对象的引用。
let num = 10;
let obj = { value: 20 };

// 在 JavaScript 中，当你赋值一个对象时，你是在复制引用。
let anotherObj = obj;
anotherObj.value = 30;
console.log(obj.value); // 30
```

```cpp !! cpp
// C++: 指针
#include <iostream>

int main() {
  int num = 10; // 一个普通的整数变量
  int* ptr = &num; // 声明一个指针 'ptr' 并存储 'num' 的地址

  // std::cout << "Value of num: " << num << std::endl; // 10
  // std::cout << "Address of num: " << &num << std::endl; // 内存地址
  // std::cout << "Value of ptr (address of num): " << ptr << std::endl; // 相同的内存地址
  // std::cout << "Value at address pointed by ptr: " << *ptr << std::endl; // 10 (解引用)

  *ptr = 20; // 通过指针更改 num 的值
  // std::cout << "New value of num: " << num << std::endl; // 20

  return 0;
}
```
</UniversalEditor>

## 引用概念和语法

**引用**是现有变量的别名（另一个名称）。一旦引用被初始化为一个变量，它就不能被重新设置以引用另一个变量。引用通常用于通过引用将参数传递给函数。

### 声明和初始化引用

*   `int& ref = variable;` // 声明一个引用 `ref` 并将其绑定到 `variable`。

<UniversalEditor title="引用基础比较" compare={true}>
```javascript !! js
// JavaScript: 对象引用在某种程度上类似于 C++ 引用。
let data = { count: 5 };
let alias = data; // 'alias' 引用与 'data' 相同的对象

alias.count = 10;
console.log(data.count); // 10

// 然而，JavaScript 引用可以被重新赋值：
alias = { count: 15 }; // 'alias' 现在引用一个新对象
console.log(data.count); // 仍然是 10
```

```cpp !! cpp
// C++: 引用
#include <iostream>

int main() {
  int value = 100; // 一个普通的整数变量
  int& ref = value; // 声明一个引用 'ref' 并将其绑定到 'value'

  // std::cout << "Value: " << value << std::endl; // 100
  // std::cout << "Value via ref: " << ref << std::endl; // 100

  ref = 200; // 通过引用更改 'value' 的值
  // std::cout << "New value: " << value << std::endl; // 200

  // 引用不能被重新设置：
  // int anotherValue = 300;
  // ref = anotherValue; // 这会将 anotherValue 的值赋值给 value，而不是重新设置 ref

  return 0;
}
```
</UniversalEditor>

## 指针算术运算

指针算术运算涉及对指针执行算术运算。它主要用于数组。

*   `ptr + n`：将指针向前移动 `n` 个元素（而不是 `n` 个字节）。
*   `ptr - n`：将指针向后移动 `n` 个元素。
*   `ptr1 - ptr2`：计算 `ptr1` 和 `ptr2` 之间的元素数量。

<UniversalEditor title="指针算术运算比较" compare={true}>
```javascript !! js
// JavaScript: 数组索引在概念上类似于指针算术运算。
let arr = [10, 20, 30, 40, 50];
console.log(arr[0]); // 10
console.log(arr[0 + 2]); // 30 (访问索引 2 的元素)
```

```cpp !! cpp
// C++: 指针算术运算
#include <iostream>

int main() {
  int arr[] = {10, 20, 30, 40, 50};
  int* ptr = arr; // ptr 指向第一个元素 (arr[0])

  // std::cout << "First element: " << *ptr << std::endl; // 10

  ptr = ptr + 2; // 将 ptr 向前移动两个整数位置 (到 arr[2])
  // std::cout << "Element after ptr + 2: " << *ptr << std::endl; // 30

  int* ptr2 = &arr[4]; // ptr2 指向 arr[4]
  // std::cout << "Elements between ptr and ptr2: " << (ptr2 - ptr) << std::endl; // 2

  return 0;
}
```
</UniversalEditor>

## 函数指针

**函数指针**是指向函数内存地址的指针。它允许你间接调用函数、将函数作为参数传递或将它们存储在数据结构中。

<UniversalEditor title="函数指针比较" compare={true}>
```javascript !! js
// JavaScript: 函数是第一类公民，可以作为参数传递。
function multiply(a, b) {
  return a * b;
}

function operate(func, x, y) {
  return func(x, y);
}

console.log(operate(multiply, 5, 4)); // 20
```

```cpp !! cpp
// C++: 函数指针
#include <iostream>

int multiply(int a, int b) {
  return a * b;
}

int operate(int (*funcPtr)(int, int), int x, int y) {
  return funcPtr(x, y);
}

// 在主函数中：
// int (*ptrToMultiply)(int, int) = &multiply; // 声明并初始化函数指针
// std::cout << operate(ptrToMultiply, 5, 4) << std::endl; // 20
```
</UniversalEditor>

## 指针和引用之间的差异

| 特性           | 指针                                  | 引用                                |
| :---------------- | :--------------------------------------- | :--------------------------------------- |
| **初始化**| 可以不初始化声明 (但危险) | 必须在声明时初始化       |
| **空值**    | 可以是 `nullptr` (或 `NULL`)             | 不能为空                           |
| **重新赋值**  | 可以重新赋值以指向不同的变量 | 不能重新设置 (始终引用相同的变量) |
| **内存地址**| 存储内存地址                  | 是别名；不存储自己的地址 (概念上) |
| **解引用**   | 需要 `*` 才能解引用              | 不需要明确的解引用运算符 (直接使用) |

## 常见指针错误

*   **悬空指针：** 指向已释放内存的指针。
*   **野指针：** 未初始化且指向任意内存位置的指针。
*   **空指针解引用：** 尝试解引用 `nullptr`，导致程序崩溃。
*   **内存泄漏：** 忘记 `delete` 动态分配的内存（模块 2 中已涵盖）。

## 与 JavaScript 引用的比较

JavaScript 没有 C++ 意义上的明确指针或引用。相反，持有对象（包括数组和函数）的 JavaScript 变量存储对这些对象的**引用**。这些引用由 JavaScript 引擎的垃圾回收器管理。

*   **无手动内存管理：** 你不需要在 JavaScript 中 `new` 或 `delete` 对象；GC 会处理它。
*   **无指针算术运算：** 你不能对 JavaScript 引用执行算术运算。
*   **引用可以重新赋值：** 与 C++ 引用不同，JavaScript 引用可以重新赋值以指向不同的对象。

本质上，JavaScript 的对象引用在某种程度上类似于 C++ 指向对象的指针，但具有自动内存管理，并且无法执行指针算术运算或直接内存访问。

---

### 练习题：
1.  解释 C++ 指针和 C++ 引用之间核心差异。何时会选择使用其中一个？
2.  编写一个 C++ 函数，该函数接受一个整数指针作为参数，将其指向的值增加 5，然后打印新值。演示如何在 `main` 中调用此函数。
3.  什么是悬空指针，以及如何在 C++ 中避免创建它？

### 项目构想：
*   使用原始指针在 C++ 中实现一个简单的动态数组（类似于 `std::vector`）。你的实现应包括添加元素、移除元素、调整数组大小和按索引访问元素的函数。密切注意内存分配和释放，以防止内存泄漏和悬空指针。
